package searchlogic

import (
	"gmap-backend/internal/util/commonutil"
	"gmap-backend/internal/util/strutil"
	"strings"
)

type MatchType struct {
	findMatch bool
	prefix    string
	extra     string
}

type ExpCreater interface {
	canHandle(strItem string) *MatchType
	create(strItem string, matchType *MatchType, quotCnt int) ExpSearcher
}

var allExpCreaters = []ExpCreater{
	&TagExpCreater{},
	&TitleExpCreater{},
	&ContentExpCreater{},
	&FullTxtExpCreater{},
	&DefExpCreater{},
}

type FullTxtExpCreater struct {
}

var fullTxtMatchPrefix = []string{"fulltext:", "fulltxt:", "full:"}
var fullTxtUnmatchPrefix = []string{"!fulltext:", "!fulltxt:", "!full:"}

func (c *FullTxtExpCreater) canHandle(strItem string) *MatchType {
	for _, prefix := range fullTxtMatchPrefix {
		if strings.HasPrefix(strItem, prefix) {
			return &MatchType{
				findMatch: true,
				prefix:    prefix,
				extra:     "",
			}
		}
	}
	for _, prefix := range fullTxtUnmatchPrefix {
		if strings.HasPrefix(strItem, prefix) {
			return &MatchType{
				findMatch: false,
				prefix:    prefix,
				extra:     "",
			}
		}
	}
	return nil
}

func (c *FullTxtExpCreater) create(strItem string, matchType *MatchType, quotCnt int) ExpSearcher {
	strItem = strutil.SubstringByRuneIndexFrom(strItem, len([]rune(matchType.prefix))) // strings.TrimSpace(strItem[len(matchType.prefix):])
	if "" == strItem {
		return nil
	}
	return &FullTxtExpSearcher{
		BaseSearcherStruct{
			Keyword:       strItem,
			SearchMatched: matchType.findMatch,
			QuotCnt:       quotCnt,
		},
	}
}

type TagExpCreater struct {
}

func (c *TagExpCreater) canHandle(strItem string) *MatchType {
	if strings.HasPrefix(strItem, "tag:") {
		return &MatchType{
			findMatch: true,
			prefix:    "tag:",
			extra:     "",
		}
	}
	if strings.HasPrefix(strItem, "!tag:") {
		return &MatchType{
			findMatch: false,
			prefix:    "!tag:",
			extra:     "",
		}
	}
	if strings.HasPrefix(strItem, "tag*:") {
		return &MatchType{
			findMatch: true,
			prefix:    "tag*:",
			extra:     "wild",
		}
	}
	if strings.HasPrefix(strItem, "!tag*:") {
		return &MatchType{
			findMatch: false,
			prefix:    "!tag*:",
			extra:     "wild",
		}
	}
	return nil
}

func (c *TagExpCreater) create(strItem string, matchType *MatchType, quotCnt int) ExpSearcher {
	strItem = strutil.SubstringByRuneIndexFrom(strItem, len([]rune(matchType.prefix))) // strings.TrimSpace(strItem[len(matchType.prefix):])
	if "" == strItem {
		return nil
	}
	return &TagExpSearcher{
		Wild: "wild" == matchType.extra,
		BaseSearcherStruct: BaseSearcherStruct{
			Keyword:       strItem,
			SearchMatched: matchType.findMatch,
			QuotCnt:       quotCnt,
		},
	}
}

type ContentExpCreater struct {
}

func (c *ContentExpCreater) canHandle(strItem string) *MatchType {
	if strings.HasPrefix(strItem, "text:") {
		return &MatchType{
			findMatch: true,
			prefix:    "text:",
			extra:     "",
		}
	}
	if strings.HasPrefix(strItem, "!text:") {
		return &MatchType{
			findMatch: false,
			prefix:    "!text:",
			extra:     "",
		}
	}
	if strings.HasPrefix(strItem, "txt:") {
		return &MatchType{
			findMatch: true,
			prefix:    "txt:",
			extra:     "",
		}
	}
	if strings.HasPrefix(strItem, "!txt:") {
		return &MatchType{
			findMatch: false,
			prefix:    "!txt:",
			extra:     "",
		}
	}
	return nil
}

func (c *ContentExpCreater) create(strItem string, matchType *MatchType, quotCnt int) ExpSearcher {
	strItem = strutil.SubstringByRuneIndexFrom(strItem, len([]rune(matchType.prefix))) // strings.TrimSpace(strItem[len(matchType.prefix):])
	if "" == strItem {
		return nil
	}
	return &ContentExpSearcher{
		BaseSearcherStruct{
			Keyword:       strItem,
			SearchMatched: matchType.findMatch,
			QuotCnt:       quotCnt,
		},
	}
}

type TitleExpCreater struct {
}

func (c *TitleExpCreater) canHandle(strItem string) *MatchType {
	if strings.HasPrefix(strItem, "title:") {
		return &MatchType{
			findMatch: true,
			prefix:    "title:",
			extra:     "",
		}
	}
	if strings.HasPrefix(strItem, "!title:") {
		return &MatchType{
			findMatch: false,
			prefix:    "!title:",
			extra:     "",
		}
	}
	return nil
}

func (c *TitleExpCreater) create(strItem string, matchType *MatchType, quotCnt int) ExpSearcher {
	strItem = strutil.SubstringByRuneIndexFrom(strItem, len([]rune(matchType.prefix))) // strings.TrimSpace(strItem[len(matchType.prefix):])
	if "" == strItem {
		return nil
	}
	return &TitleExpSearcher{
		BaseSearcherStruct{
			Keyword:       strItem,
			SearchMatched: matchType.findMatch,
			QuotCnt:       quotCnt,
		},
	}
}

type DefExpCreater struct {
}

func (c *DefExpCreater) canHandle(strItem string) *MatchType {
	if strings.HasPrefix(strItem, "!") {
		return &MatchType{
			findMatch: false,
			prefix:    "!",
			extra:     "",
		}
	}
	return &MatchType{
		findMatch: true,
		prefix:    "",
		extra:     "",
	}
}

func (c *DefExpCreater) create(strItem string, matchType *MatchType, quotCnt int) ExpSearcher {
	strItem = strutil.SubstringByRuneIndexFrom(strItem, len([]rune(matchType.prefix))) // strings.TrimSpace(strItem[len(matchType.prefix):])
	if "" == strItem {
		return nil
	}
	return &FullTxtExpSearcher{
		BaseSearcherStruct{
			Keyword:       strItem,
			SearchMatched: matchType.findMatch,
			QuotCnt:       quotCnt,
		},
	}
}

var UUID = commonutil.GetUuid()
var ESCAPE_QUOT = "__Q-" + UUID + "__"
var ESCAPE_SPACE = "__SP-" + UUID + "__"
var ESCAPE_FULL_SPACE = "__SPF-" + UUID + "__"
var PREFIX_QUOT = "__Q-PR-" + UUID + "__"
var PREFIX_BIQUOT = "__BIQ-PR-" + UUID + "__"

// splitExps 表达式拆分
// 1、对 \" 进行转义，这样再出现的 " 只为包裹作用
// 2、对每两个双引号之间的内容进行处理，把其中的半角和全角空格转义，这样再出现的空格只用于表达式的分隔
// 3、按空格拆分表达式，拆分后的每项中把转义过的内容还原
func splitExps(exp string) ([]string, bool) {
	// 对 \" 进行转义，之后再出现的 " 只为包裹作用
	// tag:"ab\"cd" -> tag:"ab___esacpe-quot___cd"
	exp = strings.ReplaceAll(exp, "\\\"", ESCAPE_QUOT)
	hasUncloseQuot := false

	// 两个引号之间的内容处理
	startPos := 0
	for {
		// 第一个引号的位置和第二个引号后的位置，这样substring能直接把两个引号包含进来
		ind1 := strings.Index(exp[startPos:], "\"")
		if ind1 < 0 {
			break
		}
		ind1 += +startPos
		ind2 := strings.Index(exp[ind1+1:], "\"")
		if ind2 >= 0 {
			ind2 += (ind1 + 1) + 1
		}

		// 只找到一个引号，则第二个位置取字符串末尾
		// 整个表达式拆分为前中后三部分，
		// 情况1：abc tag:"haha" def
		// 前面部分：abc tag:
		// 中间部分："haha"
		// 后面部分： def
		//
		// 情况2：abc tag:"haha"def
		// 前面部分：abc tag:
		// 中间部分："haha"
		// 后面部分：def
		// 如果后面部分内容如果不是半角或全角的空格开头，而是其它字符，则在前面增加一个空格，以免拆分时与后面内容算成一个词
		quotCnt := 2
		if !(ind1 < ind2) {
			ind2 = len(exp)
			quotCnt = 1
			hasUncloseQuot = true
		}
		front := exp[:ind1]
		end := exp[ind2:]
		middle := exp[ind1:ind2]
		if !strings.HasPrefix(end, " ") && !strings.HasPrefix(end, "　") {
			end = " " + end
		}

		// 两个引号中间的半角和全角空格转义，再去掉双引号，使得只有各表达式之间才会有空格，以便拆分处理
		// 替换后如果不为空串，则前面增加一个前缀标识，以便区分  txt:  "txt:"  的情况:
		// txt: -> txt:  会匹配到指定前缀，但关键词部分为空，最终不会生成有效表达式
		// "txt:" -> PREFIX-xxx-txt:  会匹配到默认创建器，最终会生成全文查找表达式（生成时会把前缀标识去掉）
		replMiddle := strings.ReplaceAll(middle, " ", ESCAPE_SPACE)
		replMiddle = strings.ReplaceAll(replMiddle, "　", ESCAPE_FULL_SPACE)
		replMiddle = strings.ReplaceAll(replMiddle, "\"", "")
		if "" != replMiddle {
			pref := PREFIX_BIQUOT
			if 1 == quotCnt {
				pref = PREFIX_QUOT
			}
			replMiddle = pref + replMiddle
		}
		exp = front + replMiddle + end
		startPos = len(front + replMiddle)
	}

	// 表达式拆分处理，拆分后的表达式中的转义过的内容替换回来
	result := make([]string, 0, 4)
	strs := strings.Split(strings.ReplaceAll(exp, "　", " "), " ")
	for _, str := range strs {
		eachExp := strings.ReplaceAll(str, ESCAPE_QUOT, "\"")
		eachExp = strings.ReplaceAll(eachExp, ESCAPE_SPACE, " ")
		eachExp = strings.ReplaceAll(eachExp, ESCAPE_FULL_SPACE, "　")
		eachExp = strings.TrimSpace(eachExp)

		if "" != eachExp {
			result = append(result, eachExp)
		}
	}
	return result, hasUncloseQuot
}

func ParseSearchers(exp string) ([]ExpSearcher, bool) {
	result := make([]ExpSearcher, 0, 4)
	strItems, hasUncloseQuot := splitExps(exp)

	for _, item := range strItems {
		handledTxt := prehandleTxt(item)
		// 使用各创建器依次判断是否可处理该表达式，一旦可处理，则无论能否解析为表达式对象，都不再尝试用下一个创建器判断
		for _, creater := range allExpCreaters {
			// 判断是否可处理该表达式：
			// 全角替换为半角后判断前缀是否匹配
			if matchType := creater.canHandle(handledTxt); nil != matchType {
				replItem := strings.ReplaceAll(strings.ReplaceAll(item, PREFIX_QUOT, ""), PREFIX_BIQUOT, "")
				quotCnt := 0
				if strings.Contains(item, PREFIX_QUOT) {
					quotCnt = 1
				} else if strings.Contains(item, PREFIX_BIQUOT) {
					quotCnt = 2
				}

				// 解析为表达式对象，如果未成功解析，则忽略该表达式，即不再使用其它创建器判断
				// 去掉双引号前缀后解析为表达式对象
				searcher := creater.create(replItem, matchType, quotCnt)
				if nil != searcher {
					result = append(result, searcher)
				}
				break
			}
		}
	}
	return result, hasUncloseQuot
}
